{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Animation;

interface

uses Types, AHMTypes, Classes, BaseControls, Containers, Animators, Colour;

type
  TAHMAbstractAnimation = class
  private
    FStartTime: TAHMTimestamp;
    FInitialised: Boolean;
  protected
    FTarget: TAHMAbstractControl;
    FDuration: Integer;
    FConcurrent: Boolean;
    FPersistent: Boolean;
    FBreakable: Boolean;
    FPercent: Single;
    FStartPercent: Single;
    FComplete: Boolean;
    FAborted: Boolean;
  protected
    function Value(StartValue, FinishValue: Integer): Integer; overload;
    function Value(StartValue, FinishValue: Single): Single; overload;
  public
    property Target: TAHMAbstractControl read FTarget write FTarget;
    property Duration: Integer read FDuration write FDuration;
    property Concurrent: Boolean read FConcurrent write FConcurrent;
    property Persistent: Boolean read FPersistent write FPersistent;
    property Breakable: Boolean read FBreakable write FBreakable;
    property Aborted: Boolean read FAborted write FAborted;
    property Complete: Boolean read FComplete;
    procedure Assign(Source: TAHMAnimation); virtual;
    procedure DoAnimation;
    procedure Initialise; virtual;
    procedure Animate; virtual;
  end;

  TAHMPauseAnimation = class(TAHMAbstractAnimation)
  private
  protected
  public
    procedure Initialise; override;
  end;

  TAHMMacroAnimation = class(TAHMPauseAnimation)
  private
    FMacroName: String;
  protected
  public
    procedure Assign(Source: TAHMAnimation); override;
    procedure Animate; override;
  end;

  TAHMFunctionAnimation = class(TAHMPauseAnimation)
  private
    FFuncName: String;
    FFuncTarget: String;
    FFuncParam: String;
  protected
  public
    procedure Assign(Source: TAHMAnimation); override;
    procedure Animate; override;
  end;

  TAHMSlideAnimation = class(TAHMAbstractAnimation)
  private
    FStartX: Single;
    FStartY: Single;
    FFinishX: Single;
    FFinishY: Single;
    FUseStart: Boolean;
    FUseFinish: Boolean;
  protected
  public
    procedure Assign(Source: TAHMAnimation); override;
    procedure Initialise; override;
    procedure Animate; override;
  end;

  TAHMSizeAnimation = class(TAHMAbstractAnimation)
  private
    FStartW: Single;
    FStartH: Single;
    FFinishW: Single;
    FFinishH: Single;
    FUseStart: Boolean;
    FUseFinish: Boolean;
  protected
  public
    procedure Assign(Source: TAHMAnimation); override;
    procedure Initialise; override;
    procedure Animate; override;
  end;

  TAHMRotateAnimation = class(TAHMAbstractAnimation)
  private
    FStartX: Single;
    FStartY: Single;
    FStartZ: Single;
    FFinishX: Single;
    FFinishY: Single;
    FFinishZ: Single;
    FUseStart: Boolean;
    FUseFinish: Boolean;
  protected
  public
    procedure Assign(Source: TAHMAnimation); override;
    procedure Initialise; override;
    procedure Animate; override;
  end;

  TAHMScaleAnimation = class(TAHMAbstractAnimation)
  private
    FStartScale: Single;
    FFinishScale: Single;
    FUseStart: Boolean;
    FUseFinish: Boolean;
  protected
  public
    procedure Assign(Source: TAHMAnimation); override;
    procedure Initialise; override;
    procedure Animate; override;
  end;

  TAHMFadeAnimation = class(TAHMAbstractAnimation)
  private
    FStartGamma: TAHMCanvasColour;
    FFinishGamma: TAHMCanvasColour;
    FUseStart: Boolean;
    FUseFinish: Boolean;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    procedure Assign(Source: TAHMAnimation); override;
    procedure Initialise; override;
    procedure Animate; override;
  end;

  TAHMFadeInAnimation = class(TAHMFadeAnimation)
  private
  protected
  public
    procedure Animate; override;
  end;

  TAHMFadeOutAnimation = class(TAHMFadeAnimation)
  private
  protected
  public
    procedure Animate; override;
  end;

  TAHMAnimationQueue = class
  private
    FAnimator: TAHMAnimator;
    FAnimations: TList;
    FControlName: String;
    FContainerName: String;
    FControl: TAHMControl;
    FContainer: TAHMContainer;
    FFinished: Boolean;
    FAborted: Boolean;
  protected
    procedure SetAnimator(Animator: TAHMAnimator);
    function GetInProgress: Boolean;
    procedure SetInProgress(InProgress: Boolean);
  public
    constructor Create;
    destructor Destroy; override;
    property ControlName: String read FControlName write FControlName;
    property ContainerName: String read FContainerName write FContainerName;
    property Animator: TAHMAnimator read FAnimator write SetAnimator;
    property AnimationInProgress: Boolean read GetInProgress write SetInProgress;
    function ValidateAnimation: Boolean;
    procedure UpdateAnimation;
    procedure Abort;
  end;

  TAHMAnimationQueueList = class
  private
    FQueue: TList;
    function GetCount: Integer;
    function GetAnimationQueue(Index: Integer): TAHMAnimationQueue;
  protected
    procedure Clear;
    procedure ClearControlAnimation(ControlName: String; Animator: TAHMAnimator);
    procedure ClearContainerAnimation(ContainerName: String; Animator: TAHMAnimator);
    function IsControlAnimating(ControlName: String; Animator: TAHMAnimator): Boolean;
    function IsContainerAnimating(ContainerName: String; Animator: TAHMAnimator): Boolean;
    procedure AddAnimation(Animation: TAHMAnimationQueue);
  public
    constructor Create;
    destructor Destroy; override;
    property Count: Integer read GetCount;
    property AnimationQueue[Index: Integer]: TAHMAnimationQueue read GetAnimationQueue; default;
    procedure AnimateControl(ControlName: String; Animator: TAHMAnimator);
    procedure AnimateContainer(ContainerName: String; Animator: TAHMAnimator);
    procedure AbortAnimation(Index: Integer);
    procedure DeleteAnimation(Index: Integer);
  end;

  TAHMAnimationEngine = class
  private
    FAnimationList: TAHMAnimationQueueList;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    procedure AnimateControl(ControlName: String; Animator: TAHMAnimator);
    procedure AnimateContainer(ContainerName: String; Animator: TAHMAnimator);
    procedure UpdateAnimations;
    function IsControlAnimating(ControlName: String; Animator: TAHMAnimator): Boolean;
    function IsContainerAnimating(ContainerName: String; Animator: TAHMAnimator): Boolean;
  end;

implementation

uses SysUtils, ApplicationManager, Timing, Places, Colours, Functions, Canvas,
     Screen, Logging, Utilities;

//----------------------------------------------------------------------------//

function TAHMAbstractAnimation.Value(StartValue, FinishValue: Integer): Integer;
begin
  // Calculate percentage shift from start value to finish value
  Result := Round(((FPercent * FinishValue) + ((100.0 - FPercent) * StartValue)) / 100.0);
end;

function TAHMAbstractAnimation.Value(StartValue, FinishValue: Single): Single;
begin
  // Calculate percentage shift from start value to finish value
  Result := ((FPercent * FinishValue) + ((100.0 - FPercent) * StartValue)) / 100.0;
end;

procedure TAHMAbstractAnimation.Assign(Source: TAHMAnimation);
begin
  // Populate animation properties from animation definition
  Duration := Source.Duration;
  Concurrent := Source.Concurrent;
  Persistent := Source.Persistent;
  Breakable := Source.Breakable;
end;

procedure TAHMAbstractAnimation.DoAnimation;
begin
  // If this is breakable anim check we've not been aborted
  if FBreakable and FAborted then
  begin
    // Animation is aborted, complete the animation without animating
    FComplete := True;
    Exit;
  end;

  // Initialise animation on first animation call
  if not FInitialised then
  begin
    // Don't perform initialisation until control loaded
    if not FTarget.Sized then Exit;

    // Record start timestamp for this animation
    FStartTime := TimerInstance.CurrentTimestamp;

    Initialise;
    FInitialised := True;
  end;

  // If this is non-persistent anim check target still onscreen
  if not FPersistent then
    with ApplicationManagerInstance.UIManager do
      if ((FTarget is TAHMContainer) and (GetContainer(FTarget.Name, True) = nil)) or
         ((FTarget is TAHMControl) and (GetControl(FTarget.Name, True) = nil)) then
      begin
        // Target is offscreen, complete the animation without animating
        FComplete := True;
        Exit;
      end;

  // Check for animation time period completed (or zero duration)
  if (FDuration = 0) or TimerInstance.HasDurationElapsed(FStartTime, FDuration) then
  begin
    FComplete := True;
    FPercent := 100.0;
  end
  else
  begin
    // Calculate percentage complete of our animation
    FPercent := 100 * (TimerInstance.CurrentTimeStamp - FStartTime) / FDuration;
  end;

  // Call animation effect
  Animate;
end;

procedure TAHMAbstractAnimation.Initialise;
begin
  // Descendants will implement initialisation steps in this method
end;

procedure TAHMAbstractAnimation.Animate;
begin
  // Descendants will implement animation effects in this method
end;

//----------------------------------------------------------------------------//

procedure TAHMPauseAnimation.Initialise;
begin
  // Pause doesn't perform any animation, but must not be concurrent
  FConcurrent := False;
end;

//----------------------------------------------------------------------------//

procedure TAHMMacroAnimation.Assign(Source: TAHMAnimation);
begin
  inherited Assign(Source);

  // Determine macro name parameter
  FMacroName := Source.GetParameter(ANIM_PARAM_MACRO_NAME);
end;

procedure TAHMMacroAnimation.Animate;
begin
  inherited Animate;

  // If animation (pause) is complete then fire the macro
  if Complete and (FMacroName <> '') then
  begin
    // Ask macro list to process macro functions
    ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.MacroList.InvokeMacro(FMacroName);
  end;
end;

//----------------------------------------------------------------------------//

procedure TAHMFunctionAnimation.Assign(Source: TAHMAnimation);
begin
  inherited Assign(Source);

  // Determine function parameters
  FFuncName   := Source.GetParameter(ANIM_PARAM_FUNCTION_NAME);
  FFuncTarget := Source.GetParameter(ANIM_PARAM_FUNCTION_TARGET);
  FFuncParam  := Source.GetParameter(ANIM_PARAM_FUNCTION_PARAM);
end;

procedure TAHMFunctionAnimation.Animate;
var
  MyFunction: TAHMFunction;
begin
  inherited Animate;

  // If animation (pause) is complete then fire the function
  if Complete and (FFuncName <> '') then
  begin
    // Lookup corresponding function object
    MyFunction := ApplicationManagerInstance.FunctionDescriptor.FunctionList.GetFunction(FFuncName);
    if Assigned(MyFunction) then
      if FFuncTarget = '' then MyFunction.Execute
                          else MyFunction.Execute(FFuncTarget, FFuncParam);
  end;
end;

//----------------------------------------------------------------------------//

procedure TAHMSlideAnimation.Assign(Source: TAHMAnimation);
var
  Param: String;
  Position: TAHMPlace;
begin
  inherited Assign(Source);

  with ApplicationManagerInstance.ThemeManager.InterfaceDescriptor do
  begin
    // Determine start position parameter
    Param := Source.GetParameter(ANIM_PARAM_START_POS);
    if Param <> '' then
    begin
      Position := PlaceList.GetPlace(Param);
      if Position <> nil then
      begin
        FStartX := Position.XPos;
        FStartY := Position.YPos;
        FUseStart := True;
      end;
    end;

    // Determine finish position parameter
    Param := Source.GetParameter(ANIM_PARAM_FINISH_POS);
    if Param <> '' then
    begin
      Position := PlaceList.GetPlace(Param);
      if Position <> nil then
      begin
        FFinishX := Position.XPos;
        FFinishY := Position.YPos;
        FUseFinish := True;
      end;
    end;
  end;
end;

procedure TAHMSlideAnimation.Initialise;
begin
  // If start parameter not set then use target position
  if not FUseStart then
  begin
    FStartX := FTarget.InitialXPos;
    FStartY := FTarget.InitialYPos;
  end;

  // If finish parameter not set then use target position
  if not FUseFinish then
  begin
    FFinishX := FTarget.InitialXPos;
    FFinishY := FTarget.InitialYPos;
  end;
end;

procedure TAHMSlideAnimation.Animate;
begin
  // Update target control position
  if Complete then
    FTarget.MoveControl(FFinishX, FFinishY)
  else
    FTarget.MoveControl(Value(FStartX, FFinishX), Value(FStartY, FFinishY));
end;

//----------------------------------------------------------------------------//

procedure TAHMSizeAnimation.Assign(Source: TAHMAnimation);
var
  Param: String;
  Width, Height: Integer;
begin
  inherited Assign(Source);

  // Determine start size parameter
  Param := Source.GetParameter(ANIM_PARAM_START_SIZE);
  if (Param <> '') and ParseStringValues(Param, Width, Height) then
  begin
    FUseStart := True;
    FStartW := ScreenInstance.ScaleVirtualX(Width);
    FStartH := ScreenInstance.ScaleVirtualY(Height);
  end
  else
    FUseStart := False;

  // Determine finish size parameter
  Param := Source.GetParameter(ANIM_PARAM_FINISH_SIZE);
  if (Param <> '') and ParseStringValues(Param, Width, Height) then
  begin
    FUseFinish := True;
    FFinishW := ScreenInstance.ScaleVirtualX(Width);
    FFinishH := ScreenInstance.ScaleVirtualY(Height);
  end
  else
    FUseFinish := False;
end;

procedure TAHMSizeAnimation.Initialise;
begin
  if not FUseStart then
  begin
    // If start parameter not set then use target size
    FStartW := FTarget.InitialWidth;
    FStartH := FTarget.InitialHeight;
  end;

  if not FUseFinish then
  begin
    // If finish parameter not set then use target size
    FFinishW := FTarget.InitialWidth;
    FFinishH := FTarget.InitialHeight;
  end;
end;

procedure TAHMSizeAnimation.Animate;
begin
  // Update target control size
  if Complete then
    FTarget.SizeControl(FFinishW, FFinishH)
  else
    FTarget.SizeControl(Value(FStartW, FFinishW), Value(FStartH, FFinishH));
end;

//----------------------------------------------------------------------------//

procedure TAHMRotateAnimation.Assign(Source: TAHMAnimation);
var
  Param: String;
  Position: TAHMPlace;
begin
  inherited Assign(Source);

  with ApplicationManagerInstance.ThemeManager.InterfaceDescriptor do
  begin
    // Determine start position parameter
    Param := Source.GetParameter(ANIM_PARAM_START_POS);
    if Param <> '' then
    begin
      Position := PlaceList.GetPlace(Param);
      if Position <> nil then
      begin
        FStartX := Position.RotationX;
        FStartY := Position.RotationY;
        FStartZ := Position.RotationZ;
        FUseStart := True;
      end;
    end;

    // Determine start position parameter
    Param := Source.GetParameter(ANIM_PARAM_FINISH_POS);
    if Param <> '' then
    begin
      Position := PlaceList.GetPlace(Param);
      if Position <> nil then
      begin
        FFinishX := Position.RotationX;
        FFinishY := Position.RotationY;
        FFinishZ := Position.RotationZ;
        FUseFinish := True;
      end;
    end;
  end;
end;

procedure TAHMRotateAnimation.Initialise;
begin
  // If start parameter not set then use target rotation
  if (not FUseStart) and (FTarget.Placement <> nil) then
  begin
    FStartX := FTarget.Placement.RotationX;
    FStartY := FTarget.Placement.RotationY;
    FStartZ := FTarget.Placement.RotationZ;
  end;

  // If finish parameter not set then use target rotation
  if (not FUseFinish) and (FTarget.Placement <> nil) then
  begin
    FFinishX := FTarget.Placement.RotationX;
    FFinishY := FTarget.Placement.RotationY;
    FFinishZ := FTarget.Placement.RotationZ;
  end;
end;

procedure TAHMRotateAnimation.Animate;
begin
  // Update target control rotation
  if Complete then
    FTarget.RotateControl(FFinishX, FFinishY, FFinishZ)
  else
    FTarget.RotateControl(Value(FStartX, FFinishX),
                          Value(FStartY, FFinishY),
                          Value(FStartZ, FFinishZ));
end;

//----------------------------------------------------------------------------//

procedure TAHMScaleAnimation.Assign(Source: TAHMAnimation);
var
  Param: String;
begin
  inherited Assign(Source);

  // Determine start scale parameter & validate
  Param := Source.GetParameter(ANIM_PARAM_START_SCALE);
  if Param <> '' then
  begin
    FStartScale := StrToFloatDef(Param, 1.0);
    if FStartScale < MIN_SCALE then FStartScale := MIN_SCALE;
    if FStartScale > MAX_SCALE then FStartScale := MAX_SCALE;
    FUseStart := True;
  end;

  // Determine finish scale parameter & validate
  Param := Source.GetParameter(ANIM_PARAM_FINISH_SCALE);
  if Param <> '' then
  begin
    FFinishScale := StrToFloatDef(Param, 1.0);
    if FFinishScale < MIN_SCALE then FFinishScale := MIN_SCALE;
    if FFinishScale > MAX_SCALE then FFinishScale := MAX_SCALE;
    FUseFinish := True;
  end;
end;

procedure TAHMScaleAnimation.Initialise;
begin
  // If start parameter not set then use target scale
  if not FUseStart then
    FStartScale := FTarget.OwnScale;

  // If finish parameter not set then use target scale
  if not FUseFinish then
    FFinishScale := FTarget.OwnScale;
end;

procedure TAHMScaleAnimation.Animate;
begin
  // Set scale factor for control
  if Complete then FTarget.OwnScale := FFinishScale
              else FTarget.OwnScale := Value(FStartScale, FFinishScale);
end;


//----------------------------------------------------------------------------//

constructor TAHMFadeAnimation.Create;
begin
  inherited Create;

  // Create colour objects to store gamma values
  FStartGamma := TAHMCanvasColour.Create;
  FFinishGamma := TAHMCanvasColour.Create;
end;

destructor TAHMFadeAnimation.Destroy;
begin
  // Free gamma colour objects
  FStartGamma.Free;
  FFinishGamma.Free;

  inherited Destroy;
end;

procedure TAHMFadeAnimation.Assign(Source: TAHMAnimation);
var
  Param: String;
  Colour: TAHMColour;
begin
  inherited Assign(Source);

  // Determine start gamma colour parameter
  Param := Source.GetParameter(ANIM_PARAM_START_GAMMA);
  if Param <> '' then
  begin
    Colour := ApplicationManagerInstance.ThemeManager.VisualDescriptor.ColourList.GetColour(Param);
    if Colour <> nil then FStartGamma.Assign(Colour.Colour);
    FUseStart := True;
  end;

  // Determine finish position parameter
  Param := Source.GetParameter(ANIM_PARAM_FINISH_GAMMA);
  if Param <> '' then
  begin
    Colour := ApplicationManagerInstance.ThemeManager.VisualDescriptor.ColourList.GetColour(Param);
    if Colour <> nil then FFinishGamma.Assign(Colour.Colour);
    FUseFinish := True;
  end;
end;

procedure TAHMFadeAnimation.Initialise;
begin
  // If start parameter not set then use target gamma
  if not FUseStart then
    FStartGamma.Assign(FTarget.OwnGamma);

  // If finish parameter not set then use target gamma
  if not FUseFinish then
    FFinishGamma.Assign(FTarget.OwnGamma);
end;

procedure TAHMFadeAnimation.Animate;
var
  i: TColourType;
  Pixel: TColourPixel;
begin
  // Compute ARGB components of gamma fade
  for i := Low(TColourType) to High(TColourType) do
    if Complete then
      Pixel[i] := FFinishGamma.Pixel[i]
    else
      Pixel[i] := Value(FStartGamma.Pixel[i], FFinishGamma.Pixel[i]);

  // Assign new gamma to target control
  FTarget.OwnGamma.Pixel := Pixel;
end;

//----------------------------------------------------------------------------//

procedure TAHMFadeInAnimation.Animate;
begin
  inherited Animate;

  // Ensure the control/container is visible before fading in
  if FTarget is TAHMAbstractControl then
    TAHMAbstractControl(FTarget).Visible := True;
end;

//----------------------------------------------------------------------------//

procedure TAHMFadeOutAnimation.Animate;
begin
  inherited Animate;

  // Hide the control/container when fade out is complete
  if Complete then
    if FTarget is TAHMAbstractControl then
      TAHMAbstractControl(FTarget).Visible := False;
end;

//----------------------------------------------------------------------------//

constructor TAHMAnimationQueue.Create;
begin
  inherited Create;

  FAnimations := TList.Create;
end;

destructor TAHMAnimationQueue.Destroy;
var
  i: Integer;
begin
  for i := 0 to Pred(FAnimations.Count) do
    TAHMAbstractAnimation(FAnimations[i]).Free;
  FAnimations.Free;

  inherited Destroy;
end;

procedure TAHMAnimationQueue.SetAnimator(Animator: TAHMAnimator);
var
  i: Integer;
  oAnim: TAHMAbstractAnimation;
begin
  // Store animator for comparison reference later
  FAnimator := Animator;
  oAnim := nil; // fix compiler warning

  // Convert animation sequence to appropriate animation implementation objects
  with Animator.Animations do
    for i := 0 to Pred(Count) do
    begin
      case GetAnimation(i).AnimationType of
        atFadeIn:   oAnim := TAHMFadeInAnimation.Create;
        atFadeOut:  oAnim := TAHMFadeOutAnimation.Create;
        atSlide:    oAnim := TAHMSlideAnimation.Create;
        atPause:    oAnim := TAHMPauseAnimation.Create;
        atMacro:    oAnim := TAHMMacroAnimation.Create;
        atFunction: oAnim := TAHMFunctionAnimation.Create;
        atRotate:   oAnim := TAHMRotateAnimation.Create;
        atScale:    oAnim := TAHMScaleAnimation.Create;
        atSize:     oAnim := TAHMSizeAnimation.Create;
        else        Continue;
      end;
      oAnim.Assign(GetAnimation(i));
      FAnimations.Add(oAnim);
    end;
end;

procedure TAHMAnimationQueue.SetInProgress(InProgress: Boolean);
begin
  // Set animation in progress flag for associated control/container
  if Assigned(FControl) then
    FControl.Animating := InProgress
  else if Assigned(FContainer) then
    FContainer.Animating := InProgress;
end;

function TAHMAnimationQueue.GetInProgress: Boolean;
begin
  // Get animation in progress flag for associated control/container
  if Assigned(FControl) then
    Result := FControl.Animating
  else if Assigned(FContainer) then
    Result := FContainer.Animating
  else
    Result := False;
end;

function TAHMAnimationQueue.ValidateAnimation: Boolean;
begin
  Result := False;

  // Clear cached control & container objects
  FControl := nil;
  FContainer := nil;

  if not FFinished then
  begin
    // Lookup required control or container in current screen stack
    if FControlName <> '' then
      FControl := ApplicationManagerInstance.UIManager.GetControl(FControlName, False)
    else
      FContainer := ApplicationManagerInstance.UIManager.GetContainer(FContainerName, False);

    // Did we find our control/container in current screen stack?
    if Assigned(FControl) or Assigned(FContainer) then
      Result := True;
  end;
end;

procedure TAHMAnimationQueue.UpdateAnimation;
var
  i: Integer;
  oAnim: TAHMAbstractAnimation;
begin
  // Assume we've finished unless we find an incomplete animation
  FFinished := True;

  // Iterate through all animations
  for i := 0 to Pred(FAnimations.Count) do
  begin
    // Skip past any complete animations
    oAnim := TAHMAbstractAnimation(FAnimations[i]);
    if oAnim.Complete then Continue;

//TODO: potential A/V if previous animation unloads control/container
    // Refresh animation target pointer
    if Assigned(FControl) then oAnim.Target := FControl
                          else oAnim.Target := FContainer;

    // Animate this and clear finished flag
    oAnim.Aborted := FAborted;
    oAnim.DoAnimation;
    FFinished := False;

    // Block on outstanding non-concurrent animations
    if (not oAnim.Complete) and (not oAnim.Concurrent) then Break;
  end;

  // If we haven't finished animating then flag control as animating
  if not FFinished then SetInProgress(True);
end;

procedure TAHMAnimationQueue.Abort;
begin
  // Set our aborted flag so we ditch any pending breakable steps
  FAborted := True;
end;

//----------------------------------------------------------------------------//

constructor TAHMAnimationQueueList.Create;
begin
  inherited Create;

  FQueue := TList.Create;
end;

destructor TAHMAnimationQueueList.Destroy;
begin
  Clear;
  FQueue.Free;

  inherited Destroy;
end;

function TAHMAnimationQueueList.GetCount: Integer;
begin
  Result := FQueue.Count;
end;

function TAHMAnimationQueueList.GetAnimationQueue(Index: Integer): TAHMAnimationQueue;
begin
  if (Index >= 0) and (Index < FQueue.Count) then
    Result := TAHMAnimationQueue(FQueue[Index])
  else
    Result := nil;
end;

procedure TAHMAnimationQueueList.Clear;
var
  i: Integer;
begin
  for i := 0 to Pred(FQueue.Count) do
    TAHMAnimationQueue(FQueue[i]).Free;
  FQueue.Clear;
end;

procedure TAHMAnimationQueueList.ClearControlAnimation(ControlName: String; Animator: TAHMAnimator);
var
  i: Integer;
  oAnimQueue: TAHMAnimationQueue;
begin
  for i := Pred(FQueue.Count) downto 0 do
  begin
    oAnimQueue := TAHMAnimationQueue(FQueue[i]);
    if (oAnimQueue.Animator = Animator) and (oAnimQueue.ControlName = ControlName) then
      AbortAnimation(i);
  end;
end;

procedure TAHMAnimationQueueList.ClearContainerAnimation(ContainerName: String; Animator: TAHMAnimator);
var
  i: Integer;
  oAnimQueue: TAHMAnimationQueue;
begin
  for i := Pred(FQueue.Count) downto 0 do
  begin
    oAnimQueue := TAHMAnimationQueue(FQueue[i]);
    if (oAnimQueue.Animator = Animator) and (oAnimQueue.ContainerName = ContainerName) then
      AbortAnimation(i);
  end;
end;

function TAHMAnimationQueueList.IsControlAnimating(ControlName: String; Animator: TAHMAnimator): Boolean;
var
  i: Integer;
  oAnimQueue: TAHMAnimationQueue;
begin
  // Set default result - not found
  Result := False;

  for i := Pred(FQueue.Count) downto 0 do
  begin
    oAnimQueue := TAHMAnimationQueue(FQueue[i]);
    if (oAnimQueue.Animator = Animator) and (oAnimQueue.ControlName = ControlName) then
    begin
      Result := True;
      Break;
    end;
  end;
end;

function TAHMAnimationQueueList.IsContainerAnimating(ContainerName: String; Animator: TAHMAnimator): Boolean;
var
  i: Integer;
  oAnimQueue: TAHMAnimationQueue;
begin
  // Set default result - not found
  Result := False;

  for i := Pred(FQueue.Count) downto 0 do
  begin
    oAnimQueue := TAHMAnimationQueue(FQueue[i]);
    if (oAnimQueue.Animator = Animator) and (oAnimQueue.ContainerName = ContainerName) then
    begin
      Result := True;
      Break;
    end;
  end;
end;

procedure TAHMAnimationQueueList.AddAnimation(Animation: TAHMAnimationQueue);
begin
  // Add animation to our queue
  FQueue.Add(Animation);

  // Perform initial validation & update of animation
  if Animation.ValidateAnimation then
    Animation.UpdateAnimation;
end;

procedure TAHMAnimationQueueList.AnimateControl(ControlName: String; Animator: TAHMAnimator);
var
  oAnimQueue: TAHMAnimationQueue;
begin
  // Abort any previous animations for this control and animator combination
  ClearControlAnimation(ControlName, Animator);

  oAnimQueue := TAHMAnimationQueue.Create;
  oAnimQueue.ControlName := ControlName;
  oAnimQueue.Animator := Animator;
  AddAnimation(oAnimQueue);
end;

procedure TAHMAnimationQueueList.AnimateContainer(ContainerName: String; Animator: TAHMAnimator);
var
  oAnimQueue: TAHMAnimationQueue;
begin
  // Abort any previous animations for this container and animator combination
  ClearContainerAnimation(ContainerName, Animator);

  oAnimQueue := TAHMAnimationQueue.Create;
  oAnimQueue.ContainerName := ContainerName;
  oAnimQueue.Animator := Animator;
  AddAnimation(oAnimQueue);
end;

procedure TAHMAnimationQueueList.AbortAnimation(Index: Integer);
begin
  // Instruct specified animation to abort
  if (Index >= 0) and (Index < FQueue.Count) then
    TAHMAnimationQueue(FQueue[Index]).Abort;
end;

procedure TAHMAnimationQueueList.DeleteAnimation(Index: Integer);
begin
  if (Index >= 0) and (Index < FQueue.Count) then
  begin
    TAHMAnimationQueue(FQueue[Index]).Free;
    FQueue.Delete(Index);
  end;
end;

//----------------------------------------------------------------------------//

constructor TAHMAnimationEngine.Create;
begin
  inherited Create;

  FAnimationList := TAHMAnimationQueueList.Create;
end;

destructor TAHMAnimationEngine.Destroy;
begin
  FAnimationList.Free;

  inherited Destroy;
end;

procedure TAHMAnimationEngine.AnimateControl(ControlName: String; Animator: TAHMAnimator);
begin
  // Delegate animation to animation list
  if Animator <> nil then
    FAnimationList.AnimateControl(ControlName, Animator);
end;

procedure TAHMAnimationEngine.AnimateContainer(ContainerName: String; Animator: TAHMAnimator);
begin
  // Delegate animation to animation list
  if Animator <> nil then
    FAnimationList.AnimateContainer(ContainerName, Animator);
end;

procedure TAHMAnimationEngine.UpdateAnimations;
var
  i: Integer;
begin
  // First check for any completed or redundant animations and delete them
  for i := Pred(FAnimationList.Count) downto 0 do
    if not FAnimationList[i].ValidateAnimation then
      FAnimationList.DeleteAnimation(i);

  // Second pass, flag all animated controls as NOT animating
  for i := 0 to Pred(FAnimationList.Count) do
    FAnimationList[i].AnimationInProgress := False;

  // Update remaining animations - one concurrent animation per control
  for i := 0 to Pred(FAnimationList.Count) do
    if not FAnimationList[i].AnimationInProgress then
      FAnimationList[i].UpdateAnimation;
end;

function TAHMAnimationEngine.IsControlAnimating(ControlName: String; Animator: TAHMAnimator): Boolean;
begin
  // Delegate request to animation queue
  Result := FAnimationList.IsControlAnimating(ControlName, Animator);
end;

function TAHMAnimationEngine.IsContainerAnimating(ContainerName: String; Animator: TAHMAnimator): Boolean;
begin
  // Delegate request to animation queue
  Result := FAnimationList.IsContainerAnimating(ContainerName, Animator);
end;


end.
